/*           Copyright(c) 2005-2008 Dave Sexton            *
 *                   HtmlEditor Control                    *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using mshtml;
using System.Windows.Forms;
using DaveSexton.Controls.Resources;

namespace DaveSexton.Controls
{
	public sealed class HtmlEditorCommands
	{
		#region Public Properties
		public HtmlCommand Undo
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Undo);
			}
		}

		public HtmlCommand Redo
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Redo);
			}
		}

		public HtmlCommand Cut
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Cut);
			}
		}

		public HtmlCommand Copy
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Copy);
			}
		}

		public HtmlCommand Paste
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Paste);
			}
		}

		public HtmlCommand Delete
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Delete);
			}
		}

		public HtmlCommand Bold
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Bold);
			}
		}

		public HtmlCommand Italic
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Italic);
			}
		}

		public HtmlCommand Underline
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Underline);
			}
		}

		public HtmlCommand Strikeout
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.StrikeThrough);
			}
		}

		public HtmlCommand JustifyLeft
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.JustifyLeft);
			}
		}

		public HtmlCommand JustifyCenter
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.JustifyCenter);
			}
		}

		public HtmlCommand JustifyRight
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.JustifyRight);
			}
		}

		public HtmlCommand JustifyNone
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.JustifyNone);
			}
		}

		public HtmlCommand Indent
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Indent);
			}
		}

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Outdent")]
		public HtmlCommand Outdent
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.Outdent);
			}
		}

		public HtmlCommand FloatRight
		{
			get
			{
				if (floatRight == null)
					floatRight = new FloatRightCommand(editor);

				return floatRight;
			}
		}

		public HtmlCommand FloatLeft
		{
			get
			{
				if (floatLeft == null)
					floatLeft = new FloatLeftCommand(editor);

				return floatLeft;
			}
		}

		public HtmlCommand ForeColor
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.ForeColor);
			}
		}

		public HtmlCommand BackColor
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.BackColor);
			}
		}

		public HtmlCommand FontName
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.FontName);
			}
		}

		public HtmlCommand FontSize
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.FontSize);
			}
		}

		public HtmlCommand InsertImage
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.InsertImage);
			}
		}

		public HtmlCommand InsertTable
		{
			get
			{
				if (insertTable == null)
					insertTable = new InsertTableCommand(editor);

				return insertTable;
			}
		}

		public HtmlCommand CreateLink
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.CreateLink);
			}
		}

		public HtmlCommand SelectAll
		{
			get
			{
				return GetCommand(HtmlCommandIdentifier.SelectAll);
			}
		}
		#endregion

		#region Private / Protected
		private HtmlCommand floatLeft, floatRight, insertTable;
		private readonly HtmlEditor editor;
		private Dictionary<HtmlCommandIdentifier, HtmlCommand> commands = new Dictionary<HtmlCommandIdentifier, HtmlCommand>();
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="HtmlEditorCommands" /> class.
		/// </summary>
		internal HtmlEditorCommands(HtmlEditor editor)
		{
			if (editor == null)
				throw new ArgumentNullException("editor");

			this.editor = editor;
		}
		#endregion

		#region Methods
		public HtmlCommand GetCommand(HtmlCommandIdentifier command)
		{
			if (!commands.ContainsKey(command))
				commands[command] = new HtmlCommand(command, editor, null);

			return commands[command];
		}
		#endregion

		#region Nested
		private sealed class FloatLeftCommand : HtmlCommand
		{
			#region Public Properties
			public override bool Enabled
			{
				get
				{
					return !string.Equals(Editor.domDocument.selection.type, "none", StringComparison.OrdinalIgnoreCase);
				}
			}

			public override bool Supported
			{
				get
				{
					return true;
				}
			}

			public override bool Indeterminate
			{
				get
				{
					return SelectedElementsInternal.Count != 1;
				}
			}

			public override string Text
			{
				get
				{
					return null;
				}
			}

			public override bool Activated
			{
				get
				{
					return SelectedElementsInternal[0].style.styleFloat == "left";
				}
			}
			#endregion

			#region Constructors
			/// <summary>
			/// Constructs a new instance of the <see cref="FloatLeftCommand" /> class.
			/// </summary>
			public FloatLeftCommand(HtmlEditor editor)
				: base(HtmlCommandIdentifier.None, editor)
			{
				// this property is only set to reflect the behavior of this command
				// - it handles its own execution and notification
				NotifyHtmlChangedOnExecute = true;
			}
			#endregion

			#region Methods
			public override bool Execute(bool showUI, object value)
			{
				IHTMLElement element = SelectedElementsInternal[0];

				if (element.style.styleFloat == "left")
					element.style.styleFloat = null;
				else
					element.style.styleFloat = "left";

				NotifyHtmlChanged();

				return true;
			}

			public override T GetValue<T>(T defaultValue)
			{
				return defaultValue;
			}
			#endregion
		}

		private sealed class FloatRightCommand : HtmlCommand
		{
			#region Public Properties
			public override bool Enabled
			{
				get
				{
					return !string.Equals(Editor.domDocument.selection.type, "none", StringComparison.OrdinalIgnoreCase);
				}
			}

			public override bool Supported
			{
				get
				{
					return true;
				}
			}

			public override bool Indeterminate
			{
				get
				{
					return SelectedElementsInternal.Count != 1;
				}
			}

			public override string Text
			{
				get
				{
					return null;
				}
			}

			public override bool Activated
			{
				get
				{
					return SelectedElementsInternal[0].style.styleFloat == "right";
				}
			}
			#endregion

			#region Constructors
			/// <summary>
			/// Constructs a new instance of the <see cref="FloatRightCommand" /> class.
			/// </summary>
			public FloatRightCommand(HtmlEditor editor)
				: base(HtmlCommandIdentifier.None, editor)
			{
				// this property is only set to reflect the behavior of this command
				// - it handles its own execution and notification
				NotifyHtmlChangedOnExecute = true;
			}
			#endregion

			#region Methods
			public override bool Execute(bool showUI, object value)
			{
				IHTMLElement element = SelectedElementsInternal[0];

				if (element.style.styleFloat == "right")
					element.style.styleFloat = null;
				else
					element.style.styleFloat = "right";

				NotifyHtmlChanged();

				return true;
			}

			public override T GetValue<T>(T defaultValue)
			{
				return defaultValue;
			}
			#endregion
		}

		private sealed class InsertTableCommand : HtmlCommand
		{
			#region Public Properties
			public override bool Enabled
			{
				get
				{
					return (Editor.domDocument.selection != null && !string.Equals(Editor.domDocument.selection.type, "none", StringComparison.OrdinalIgnoreCase))
						|| Editor.DefaultEditableElement != null;
				}
			}

			public override bool Supported
			{
				get
				{
					return true;
				}
			}

			public override bool Indeterminate
			{
				get
				{
					return false;
				}
			}

			public override string Text
			{
				get
				{
					return null;
				}
			}

			public override bool Activated
			{
				get
				{
					return false;
				}
			}
			#endregion

			#region Private / Protected
			#endregion

			#region Constructors
			/// <summary>
			/// Constructs a new instance of the <see cref="InsertTableCommand" /> class.
			/// </summary>
			public InsertTableCommand(HtmlEditor editor)
				: base(HtmlCommandIdentifier.None, editor)
			{
				// this property is only set to reflect the behavior of this command
				// - it handles its own execution and notification
				NotifyHtmlChangedOnExecute = true;
			}
			#endregion

			#region Methods
			private static InsertTableCommandSettings ConvertSettings(object settings)
			{
				// set number of columns only, both columns and rows, or use defaults

				InsertTableCommandSettings itc = settings as InsertTableCommandSettings;

				if (itc == null)
				// default settings
				{
					return new InsertTableCommandSettings(
						"border-collapse: collapse; border: 2px solid gray; width: 100%; height: 200px;",
						new Size(3, 1));		// default 3 columns, 1 row
				}
				else
					// clone settings
					return new InsertTableCommandSettings(itc.CssStyle, itc.Dimensions);
			}

			private Size GetDimensionsFromUser(Size defaultDimensions, out bool success)
			{
				success = false;

				using (HtmlTableForm dialog = new HtmlTableForm())
				{
					dialog.SelectedColumnCount = defaultDimensions.Width;
					dialog.SelectedRowCount = defaultDimensions.Height;

					if (dialog.ShowDialog(Editor) != DialogResult.OK)
						return Size.Empty;

					success = true;
					return new Size(dialog.SelectedColumnCount, dialog.SelectedRowCount);
				}
			}

			private IHTMLElement GetParentElement(out bool isBookmark)
			{
				isBookmark = false;

				if (string.Equals(Editor.domDocument.selection.type, "none", StringComparison.OrdinalIgnoreCase))
				// append table to the end of the editableElement since there is no selection
				{
					HtmlElement element = Editor.DefaultEditableElement;

					return (element == null) ? null : (IHTMLElement) element.DomElement;
				}

				isBookmark = true;

				// variable to hold reference to the newly created placeholder
				// obtaining the reference depends on the type of the selection
				IHTMLElement placeholder;

				if (string.Equals(Editor.domDocument.selection.type, "text", StringComparison.OrdinalIgnoreCase))
				// text range selected
				{
					IHTMLTxtRange range = (IHTMLTxtRange) Editor.domDocument.selection.createRange();

					// create placeholder and insert selection
					range.execCommand("CreateBookmark", false, ((IHTMLDocument3) Editor.domDocument).uniqueID);

					// expand to new bookmark element
					placeholder = range.parentElement();
				}
				else
				// control range selected
				{
					IHTMLControlRange range = (IHTMLControlRange) Editor.domDocument.selection.createRange();

					// create placeholder and insert selection
					range.execCommand("CreateBookmark", false, ((IHTMLDocument3) Editor.domDocument).uniqueID);

					// expand to new bookmark element
					placeholder = range.commonParentElement();
				}

				return placeholder;
			}

			private IHTMLTable CreateTable(InsertTableCommandSettings settings)
			{
				IHTMLTable table = (IHTMLTable) Editor.domDocument.createElement("table");
				table.border = "2px";

				// set the style
				((IHTMLElement) table).style.cssText = settings.CssStyle;

				int rowHeightPercentage = 100 / settings.Dimensions.Height;
				int cellWidthPercentage = 100 / settings.Dimensions.Width;

				// build table
				for (int r = 0; r < settings.Dimensions.Height; r++)
				{
					IHTMLTableRow row = (IHTMLTableRow) table.insertRow(r);
					((IHTMLTableRow2) row).height = rowHeightPercentage + "%";

					for (int c = 0; c < settings.Dimensions.Width; c++)
					{
						IHTMLTableCell cell = (IHTMLTableCell) row.insertCell(c);
						cell.width = cellWidthPercentage + "%";
					}
				}

				return table;
			}

			private static void InsertTable(IHTMLTable table, IHTMLElement parentElement, bool isBookmark)
			{
				IHTMLDOMNode node = parentElement as IHTMLDOMNode;
				IHTMLDOMNode tableNode = table as IHTMLDOMNode;

				if (!isBookmark)
				// simply append the table as a child of the parentElement
				{
					node.appendChild(tableNode);
					return;
				}

				// isBookmark is true, indicating that there was a document selection and that a bookmark had
				// to be created as a placeholder for the table.  the contents of the bookmark must be copied
				// into the table (cell 0,0) and then the node replaced with the table itself.

				// get a reference to the parentElement's children
				IHTMLDOMChildrenCollection children = (IHTMLDOMChildrenCollection) node.childNodes;

				// cell at 0, 0
				IHTMLDOMNode firstCell = (IHTMLDOMNode) ((IHTMLTableRow) table.rows.item(null, 0)).cells.item(null, 0);

				for (int i = 0; i < children.length; )
				// copy children to cell
				{
					IHTMLDOMNode child = (IHTMLDOMNode) children.item(i);

					firstCell.appendChild(child.removeNode(true));
				}

				// replace bookmark with table
				node.replaceNode(tableNode);
			}

			public override bool Execute(bool showUI, object value)
			{
				// create default settings or clone the settings from the specified value
				InsertTableCommandSettings settings = ConvertSettings(value);

				// must get the parentElement reference before showing the UI
				// since the WebBrowser will lose focus when the Table dialog is 
				// show, causing the document.selection to become null
				bool isBookmark = false;
				IHTMLElement parentElement = GetParentElement(out isBookmark);

				if (parentElement == null)
					throw new InvalidOperationException(Errors.InsertTableCommandInvalidSelection);

				if (showUI)
				{
					bool success;
					settings.Dimensions = GetDimensionsFromUser(settings.Dimensions, out success);

					if (!success)
						return false;
				}

				// create table
				IHTMLTable table = CreateTable(settings);

				// add table to document
				InsertTable(table, parentElement, isBookmark);

				((IHTMLElement) table).scrollIntoView(true);

				NotifyHtmlChanged();

				return true;
			}

			public override T GetValue<T>(T defaultValue)
			{
				return defaultValue;
			}
			#endregion
		}
		#endregion
	}
}
